package com.dianrong.common.uniauth.server.track;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.dianrong.common.uniauth.common.cons.AppConstants;
import com.dianrong.common.uniauth.server.data.entity.Audit;
import com.dianrong.common.uniauth.server.data.mapper.AuditMapper;
import com.dianrong.common.uniauth.server.util.RegExpUtil;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class GlobalVarQueue {

    @Autowired
    private AuditMapper auditMapper;

    private BlockingQueue<GlobalVar> GLOBALVAR_QUEUE = new ArrayBlockingQueue<GlobalVar>(AppConstants.GLOBALVAR_QUEUE_SIZE);

    private Object lock = new Object();

    private GlobalVarQueue() {

    }

    public void add(GlobalVar gv) {
        boolean isSuccess = GLOBALVAR_QUEUE.offer(gv);
        if (!isSuccess) {
            log.error("Can not add " + gv + " into global queue.");
        }
    }

    @PostConstruct
    private void init() {
        new Thread() {
            public void run() {
                List<Audit> auditList = new ArrayList<Audit>();
                new SaveToDbThread(auditList).start();

                while (true) {
                    try {
                        GlobalVar gv = GLOBALVAR_QUEUE.take();
                        if (gv != null) {
                            Audit audit = new Audit();
                            audit.setReqDate(gv.getReqDate());
                            audit.setReqIp(gv.getIp());
                            audit.setReqElapse(gv.getElapse());
                            String expInfo = gv.getException();
                            if (expInfo != null && expInfo.length() > AppConstants.AUDIT_INSERT_EXP_LENGTH) {
                                expInfo = expInfo.substring(0, AppConstants.AUDIT_INSERT_EXP_LENGTH);
                            }
                            audit.setReqExp(expInfo);
                            audit.setReqMethod(gv.getMethod());

                            String reqParam = gv.getReqParam();
                            if (reqParam != null && reqParam.length() > AppConstants.AUDIT_INSET_PARAM_LENGTH) {
                                reqParam = reqParam.substring(0, AppConstants.AUDIT_INSET_PARAM_LENGTH);
                            }
                            audit.setReqParam(RegExpUtil.purgePassword(reqParam));
                            audit.setReqSuccess(gv.getSuccess());
                            audit.setReqUrl(gv.getReqUrl());
                            audit.setReqUuid(gv.getUuid());
                            audit.setUserId(gv.getUserId());
                            audit.setReqClass(gv.getMapper());
                            audit.setReqSeq(gv.getInvokeSeq());
                            audit.setDomainId(gv.getDomainId());
                            audit.setTenancyId(gv.getTenancyId());
                            audit.setRequestDomainCode(gv.getRequestDomainCode());
                            synchronized (lock) {
                                auditList.add(audit);
                            }
                        }
                    } catch (Exception e) {
                        log.error("Take from GLOBALVAR_QUEUE error.", e);
                    }
                }
            }
        }.start();
    }

    private class SaveToDbThread extends Thread {
        private List<Audit> toBeInsertedAuditList;

        public SaveToDbThread(List<Audit> toBeInsertedAuditList) {
            this.toBeInsertedAuditList = toBeInsertedAuditList;
        }

        public void run() {
            List<Audit> insertAuditList = new ArrayList<Audit>();
            while (true) {
                try {
                    sleep(AppConstants.AUDIT_INSERT_EVERY_SECOND * 1000L);
                    synchronized (lock) {
                        insertAuditList.addAll(toBeInsertedAuditList);
                        toBeInsertedAuditList.clear();
                    }

                    int size = insertAuditList.size();
                    log.debug("Size for insertAuditList:" + insertAuditList.size());
                    if (size > 0) {
                        int start = 0;
                        int end = AppConstants.AUDIT_INSERT_LIST_SIZE;
                        while (true) {
                            if (end > size) {
                                end = size;
                            }
                            try {
                                auditMapper.insertBatch(insertAuditList.subList(start, end));
                            } catch (Exception e) {
                                log.error("Inner:Batch insert db error.", e);
                            }
                            if (end == size) {
                                break;
                            }
                            start = end;
                            end += AppConstants.AUDIT_INSERT_LIST_SIZE;
                        }
                        insertAuditList.clear();
                    }
                } catch (Exception e) {
                    log.error("Sleep error.", e);
                }
            }
        }
    }
}
